import type {
    AsyncHandle,
    PromiseParamsObj,
    CallBack,
    generator,
    GeneratorComposer,
    PriorComposer,
    ReactiverLike,
    UndertakeComposer,
} from "../types";
import Composer from "./composer";
import { isClosed } from "../utils/handle";
import Prior from "./prior";
import Undertake from "./undertake";

export default class Generator extends Composer implements GeneratorComposer {

    private prior: PriorComposer ;

    private undertake: UndertakeComposer;

    private through: boolean = false

    constructor(reactiver: ReactiverLike) {
        super(reactiver);
        this.prior = new Prior(this.reactiver);
        this.undertake = new Undertake(this.reactiver);
    }

    private generatorList: Array<generator> = [];

    set(this: Generator,
        val: AsyncHandle,
        suffix?: PromiseParamsObj
    ) {

        let baseCaller = this
        this.generatorList.push({
            mainstay: (result?: any) => {
                let params = suffix?.params || []
                result !== undefined && params.push(result)
                return val.bind(suffix?.caller || baseCaller, ...params);
            },
            aftler: (result?: any,) => {
                return suffix?.callback?.bind(suffix?.caller || baseCaller, result, (function (this: Generator) {this.through = true }).bind(baseCaller))
            },
            aftlerSource: suffix?.callback
        });
    }

    setPrior(val: CallBack, caller?: any) {
        this.prior.set(val, caller)
    }

    setUndertake(val: CallBack, caller?: any) {
        this.undertake.set(val, caller)
    }

    destroy(): void {
        throw new Error("Method not implemented.");
    }

    async run() {

        if (isClosed(this.reactiver)) {
            return;
        }
        try {

            let result = this.prior.run();
            for (const generator of this.generatorList) {
                if (isClosed(this.reactiver)) break;
                result = await this.core(generator, result)
               
                if (this.through) break;
            }
            return this.end(result)
        } catch (err: any) {
            this.reactiver.closed = true;
            throw new Error(err.message || err);
        }
    }

    async core(generator: generator, result: any) {
       
        result = await generator.mainstay(result)();
        if (generator.aftlerSource) {
            result = generator.aftler(result)?.();
            if (result === undefined) {
                throw new Error("generator After must have a return!");
            }
        }

        return result
    }

    end(result: any) {
        return this.undertake?.run(result) || result
    }

}
